/**

This file is part of MaCI/GIMnet.

MaCI/GIMnet is free software: you can redistribute it and/or modify it 
under the terms of the GNU Lesser General Public License as published 
by the Free Software Foundation, either version 3 of the License, or 
(at your option) any later version.

MaCI/GIMnet is distributed in the hope that it will be useful, but WITHOUT 
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public 
License for more details.

You should have received a copy of the GNU Lesser General Public 
License along with GIMnet. (See COPYING.LESSER) If not, see 
<http://www.gnu.org/licenses/>.

**/
/**
 * \file
 * \brief Implementation for MaCICtrl interface client library.
 * \author Antti Maula <antti.maula@tkk.fi>
 *
 * \todo Proper implementation for the 'GetInformation'. The redirection
 *       buffer hack used while receiving is not very nice. New GIMI feature
 *       required?
 */

#include "MaCICtrlClient.hpp"
#include "MaCICtrlData.hpp"
#include <stdlib.h>
#include <time.h>
#include <algorithm>

//*****************************************************************************
using namespace MaCI::MaCICtrl;
//*****************************************************************************
//*****************************************************************************
#define GIMI_CLIENT_API (20000)
#include "gimi.h"
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************

CMaCICtrlClient::CMaCICtrlClient(gimi::GIMI *aGIMIPtr, const int aMinor)
  : CSync(1,1),
    iClientActive(false),
    iGIMIPtr(aGIMIPtr),
    iInterfaceMajor(GIMI_PROTOCOL_MACI_MACICTRL),
    iRedirectionBufferId(-1)
{
}
//*****************************************************************************

CMaCICtrlClient::~CMaCICtrlClient()
{
}
//*****************************************************************************

MaCI::EMaCIError CMaCICtrlClient::Open(void)
{
  if (!iClientActive) {
    iClientActive = true;

    // Initialize random number generator
    ownRandom_init_time();
  }
  return KMaCIOK;
}
//*****************************************************************************

MaCI::EMaCIError CMaCICtrlClient::Close(void)
{
  if (iClientActive) {
    iClientActive = false;
  }
  return KMaCIOK;
}
//*****************************************************************************

MaCI::EMaCIError CMaCICtrlClient::DiscoverMaCICtrlServices(gimi::t_serviceList &aServiceList,
                                                           const unsigned int aTimeout_ms) const
{
  EMaCIError result = KMaCIError;
  int r;
  gimi::t_serviceList lserv;

  assert(iGIMIPtr != NULL);

  // Do full-scale servicediscovery
  dPrint(10, "Doing serviceDiscovery with timeout of %ums",
         aTimeout_ms);
  r = iGIMIPtr->serviceDiscovery(lserv, aTimeout_ms);

  if (r != GIMI_OK) {
    // GIMI returned error?
    switch(r) {
    case GIMI_TIMEOUT:
      result = KMaCITimeout;
      break;
    case GIMI_INVALIDCLIENT:
      result = KMaCIUnknownClient;
      break;
    case GIMI_NOCONNECTION:
      result = KMaCINoConnection;
      break;
    default:
      break;
    }

    // Any error, print warning and sleep for one second.
    dPrint(6,"GIMI Error (%d, '%s') received while attempting 'serviceDiscovery'.",
           r, gimi::getGimiErrorString(r).c_str());

  } else {
    dPrint(10,"Got Servicelist from GIMI (Contains %d entries)",
           lserv.size());

    // Clear the output serviceList
    aServiceList.clear();

    // Gimi ServiceDiscovery ok.
    for(EACH_IN_i(lserv)) {
      // Take easier-to-use reference
      const gimi::GIMIService &s = *i;

      // Test the type and major
      if (s.serviceType == GIMI_SERVICETYPE_ACCEPTED &&
          s.majorServiceId == GIMI_PROTOCOL_MACI_MACICTRL) {
        // Servicetype is 'ACCEPTED' and Service Major is MaCICtrl
        // No furher processing required, so push the element in outputVec.
        aServiceList.push_back(s);
        dPrint(10, "Added supported MaCICtrl Accepted-service at node '%s' (Minor %u)",
               s.clientName.c_str(),
               s.minorServiceId);

      } else {
        dPrint(15,"Ignored node '%s', ServiceType not supported. (Type: '%s', Major: '%u')",
               s.clientName.c_str(),
               s.serviceType==GIMI_SERVICETYPE_ACCEPTED?"ACCEPTED":"PROVIDED",
               s.majorServiceId);

      }
    }

    // Finally, set the result.
    result = KMaCIOK;
  }

  return result;
}
//*****************************************************************************

bool CMaCICtrlClient::DecodeServiceMessage(TServiceEntryArray &aServiceArray,
                                           const gimi::GIMIMessage &aMsg) const
{
  bool result = false;
  CMaCICtrlData mcd;
  gim::binbag::CBinBag bb;
  if (bb.Decode(aMsg.getData(), aMsg.getDatalength()) &&
      mcd.DecodeFrom(&bb)) {
    // Decoded OK, Now extract information

    // Verify first that this structure in fact is of correct type.
    const TCommand *cmd = mcd.GetCommand();
    if (!cmd || cmd->cmd != KCommandTellInformation) {
      dPrint(1,"Warning: Invalid type of Information message received!");

    } else {
      // Empty array
      aServiceArray.clear();

      // Now decode.
      const TDeviceGroup *gid = mcd.GetDeviceGroup();
      const TGroupID *gid_old = mcd.GetGroupID(); /// For backwards compability support.
      const int servicecount = mcd.GetServiceCount();
      unsigned int gimnetID = aMsg.senderId;
      std::string gimnetName;
      iGIMIPtr->findClientName(gimnetName, aMsg.senderId);

      // Check for GroupID
      if (gid || gid_old) {

        // Now loop through services.
        for(int i=0; i < servicecount; ++i) {
          const TService *s = mcd.GetService(i);

          // Check for service.
          if (s) {
            // Get Identification
            const TServiceIdentification *sid = mcd.GetServiceIdentification(*s);

            SServiceEntry se;
            se.gimnetname = gimnetName;
            se.gimnetid = gimnetID;

            // Use correct group information
            if (gid) {
              se.devicegroup = std::string(gid->text, gid->length-1);

            } else if (gid_old) {
              // Backwards compability for old GID types.
              dPrintLCYellow(ODWARN,"Warning: Found service using obsolete type TGroupID, please recompile the service module with GroupID '%s'",
                             gid_old->name);
              se.devicegroup = gid_old->name;

            }


            // Back to normal use. Maybe Nokia internet tablet is
            // fixed already :)
            se.service = *s;
            /*
              se.service.servicemajor=s->servicemajor;
              se.service.serviceminor=s->serviceminor;
              se.service.type=s->type;
              se.service.flags=s->flags;
            */

            if (sid) se.identification = *sid;
            aServiceArray.push_back(se);
          }
        }
      }

      // Mark operation OK.
      result = true;
    }
  }

  return result;
}
//*****************************************************************************

bool CMaCICtrlClient::DecodeAccessMessage(TAccess &aAccess,
                                          const gimi::GIMIMessage &aMsg) const
{
  bool result = false;
  CMaCICtrlData mcd;
  gim::binbag::CBinBag bb;

  if (bb.Decode(aMsg.getData(), aMsg.getDatalength()) &&
      mcd.DecodeFrom(&bb)) {
    // Decoded OK, Now extract information
    // Verify first that this structure in fact is of correct type.
    const TCommand *cmd = mcd.GetCommand();
    if (!cmd || cmd->cmd != KCommandTellAccess) {
      dPrint(1,"Warning: Invalid type of Information message received!");
    } else {
      const TAccess *aptr = mcd.GetAccess();
      if (aptr) {
        dPrint(10,"Succesfully got TAccess structure. Copy to caller.");
        aAccess = *aptr;
        result = true;
      }
    }
  }

  return result;
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::GetServiceArray(TServiceEntryArray &aServices,
                                 const unsigned int &aGIMnetName,
                                 const int aMinor,
                                 const unsigned int aTimeout_ms) const
{
  std::string targetName;
  MaCI::EMaCIError result = KMaCIError;
  const ownTime_ms_t begin = ownTime_get_ms();
  ownTime_ms_delta_t tleft = aTimeout_ms;

  if (iGIMIPtr->findClientName(targetName, aGIMnetName, tleft)) {
    // Update tleft.
    tleft = ownTime_get_ms_left(aTimeout_ms, begin);

    // Call GetServiceArray
    result = GetServiceArray(aServices, targetName, aMinor, aTimeout_ms);
  }

  return result;
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::GetServiceArray(TServiceEntryArray &aEntryArray,
                                 const std::string &aGIMnetName,
                                 const int aMinor,
                                 const unsigned int aTimeout_ms) const
{
  gimi::t_serviceList sl;

  // We know only name and minorServiceId fields are used, so construct like this.
  sl.push_back(gimi::GIMIService(0,
                                 aGIMnetName,
                                 0,
                                 iInterfaceMajor,
                                 aMinor));

  return GetServiceArray(aEntryArray, sl, aTimeout_ms);
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::GetServiceArray(TServiceEntryArray &aEntryArray,
                                 const gimi::t_serviceList &aServiceList,
                                 const unsigned int aTimeout_ms) const
{
  using namespace MaCI;
  EMaCIError result = KMaCIOK;


  //dPrintLCRed(0, "aTimeout_ms is %u", aTimeout_ms);

  // Create request.
  int requests_sent = 0;
  const ownTime_ms_t tbegin = ownTime_get_ms();
  const int uniq_minor = 100000 + (random() % 100000);
  CMaCICtrlData req;
  req.CreateInternalBinBag();
  req.SetCommand(KCommandGetInformation);

  // Reset
  aEntryArray.clear();

  dPrint(8,"Using interface '%d:%d' for GetInformation burst",
         iInterfaceMajor, uniq_minor);

  // Add Accepted service
  iGIMIPtr->addAcceptedService(iInterfaceMajor,
                               uniq_minor,
                               "MaCI - MaCICtrlClient (No commands accepted - don't bother)");

  // Create local redirection to allow getting right answer back
  const unsigned int redir = iGIMIPtr->reserveBufferIdentifier();
  iGIMIPtr->addRedirection(redir,
                           iInterfaceMajor,
                           uniq_minor);

  // Modify Queuelen
  iGIMIPtr->setQueueMaxSize(iInterfaceMajor,
                            uniq_minor,
                            1000000,
			    redir);

  // Now, send the request to ALL entries at once!
  for(EACH_IN_i(aServiceList)) {
    if (i->majorServiceId == iInterfaceMajor &&
        i->minorServiceId >= 0 && i->minorServiceId < 65535) {
      
      /*      dPrint(ODTEST,"Sent: \t%d:%d,\t'%08x',\t'%s'\t%d",
             i->majorServiceId,
             i->minorServiceId,
             i->clientId,
             i->clientName.c_str(),
             i->serviceType);
      */

      // Send the request
      dPrint(8,"Sending Request to client: '%s'", i->clientName.c_str());
      const bool r = iGIMIPtr->send(i->clientName,
                                    req.GetBinBagContainerPtr()->GetBuffer(),
                                    req.GetBinBagContainerPtr()->GetBufferSize(),
                                    iInterfaceMajor,
                                    i->minorServiceId,
                                    uniq_minor) == GIMI_OK;
      if (!r) {
        dPrint(6,"Warning: Sending to client '0x%08x' failed!", i->clientId);

      } else {
        ++requests_sent;
        dPrint(8,"%d requests sent.", requests_sent);

      }
    } else {
      dPrint(6,"Not sending Request to '%s', minor: '%u', appears to be Client.",
             i->clientName.c_str(), i->minorServiceId);

    }
  }

  // Print debuginfo.
  dPrint(ODTEST,"Total of %d requests sent.", requests_sent);

  // Requests sent, now wait for replies
  gimi::GIMIMessage msg;
  TServiceEntryArray entrya;


  // Now receive MAXIMUM of 'replies received' messages.
  for(int replies_received = 0;
      replies_received < requests_sent;
      ++replies_received) {
    const int time_left =  aTimeout_ms - ownTime_get_ms_since(tbegin);
    if (time_left > 0) {
      const bool gr = iGIMIPtr->receive(msg,
                                        time_left,
                                        iInterfaceMajor,
                                        uniq_minor,
                                        redir) == GIMI_OK;
      // Check whether we got a Message.
      if (gr) {
        /*        dPrint(ODTEST,"Received: \t%d:%d,\t'%08x'",
               msg.majorTypeId,
               msg.minorTypeId,
               msg.senderId);
        */


        // Decode the GIMI message as MaCICtrl message
        bool tr = DecodeServiceMessage(entrya, msg);
        if (tr) {
          dPrint(8,"Reply received and parsed OK.");
          aEntryArray.insert(aEntryArray.end(), entrya.begin(), entrya.end());

        } else {
          dPrint(8,"Failed to Decode received message!");
          --replies_received;

        }

      } else { // if (gr) ...
        dPrint(6,"Failed to receive messages");
        // decrementing replies received counter, as this was
        --replies_received;
      }

    } else { // if (time_left > 0) ...
      dPrintLCRed(ODTEST,"MaCI Service Discovery timed out, not all replies were received (Got %d/%d)",
                  replies_received, requests_sent);
      break;
      
    }
    dPrint(8, "replies_received == %d, requests_sent == %d", replies_received, requests_sent);
  }


  // Remove the local redirection
  iGIMIPtr->removeRedirection(redir,
                              iInterfaceMajor,
                              uniq_minor);
  iGIMIPtr->releaseBufferIdentifier(redir);

  // Remove accepted service
  iGIMIPtr->removeAcceptedService(iInterfaceMajor,
                                  uniq_minor);


  return result;
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::FilterServices(TServiceEntryArray &aDestServices,
                                const TServiceEntryArray &aSrcServices,
                                const SMaCISL &aMaCISL)
{
  // Error state
  MaCI::EMaCIError result = KMaCIOK;

  // Take easier (faster) to reference variables about the filter containers.
  const bool gotGroupFilter = aMaCISL.group.size() > 0;
  const bool gotInterfaceFilter = aMaCISL.interface.size() > 0;
  const bool gotInstanceFilter = aMaCISL.instance.size() > 0;

  // Clear destination
  aDestServices.clear();

  // Iterate source list.
  for(EACH_IN_i(aSrcServices)) {
    bool accept = true;

    // Group test
    if (gotGroupFilter) {
      if (i->devicegroup != aMaCISL.group) accept = false;
    }

    // Interface test
    if (accept && gotInterfaceFilter) {
      std::string dtname;
      const bool v = gimi::datatypeDefinitions.getDatatypeName(dtname, i->service.servicemajor);
      if (v == false || dtname != aMaCISL.interface) accept = false;
    }

    // Instance test
    if (accept && gotInstanceFilter) {
      if (i->identification.interfaceinstancename != aMaCISL.instance) accept = false;
    }

    // If accept still 'true', push element to output array.
    if (accept) {
      aDestServices.push_back(*i);
    }
  }

  return result;
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::FindService(SServiceEntry &aEntry,
                             const TServiceEntryArray &aSrcServices,
                             const SMaCISL &aMaCISL)
{
  MaCI::EMaCIError result = KMaCIError;

  TServiceEntryArray array;
  FilterServices(array, aSrcServices, aMaCISL);
  if (array.size() == 1) {
    // Got single entry. Return it right away.
    aEntry = array.front();
    result = KMaCIOK;

  } else if (array.size() == 2) {
    if (array.front().service.type == KServiceTypeAccepted &&
        array.back().service.type == KServiceTypeProvided) {
      aEntry = array.front();
      result = KMaCIOK;

    } else if (array.front().service.type == KServiceTypeProvided &&
        array.back().service.type == KServiceTypeAccepted) {
      aEntry = array.back();
      result = KMaCIOK;

    } else {
      dPrint(1,"Invalid service types! Multiple identical service entries in Network?");

    }

  } else {
    dPrint(2,"Filtering resulted in %u entries! (Expected 1 or 2)",
           array.size());

  }

  return result;
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::FindMaCISL(SMaCISL &aMaCISL,
                            const TServiceEntryArray &aSrcServices,
                            const std::string &aGIMnetName,
                            const unsigned int aMajor,
                            const unsigned int aMinor,
                            const MaCICtrl::EServiceType aServiceType)
{

  MaCI::EMaCIError result = KMaCIError;

  if (aServiceType == KServiceTypeAcceptedToo) {
    dPrintLC(ODWARN, ODC_BRIGHT, ODC_YELLOW, ODC_BLACK,
             "FindMaCISL was given ServiceType value in GIMI format, will not proceed.");

  } else if (aGIMnetName.size() &&
             aMajor > 0 &&
             aMinor >= 0) {

    // Iterate through all elements.
    for(EACH_IN_i(aSrcServices)) {

      // Take easier-to-use reference.
      const SServiceEntry &se = *i;

      // Check GIMnetName for match.
      if (se.gimnetname == aGIMnetName &&
          se.service.servicemajor == aMajor &&
          se.service.serviceminor == aMinor &&
          ( se.service.type == aServiceType || aServiceType == KServiceTypeAny) ) {
        // Match!!! Copy details.
        aMaCISL = SMaCISL(se.devicegroup,
                          se.service.servicemajor,
                          se.identification.interfaceinstancename);
        result = KMaCIOK;
      }
    }

  } else {
    dPrint(ODERROR,"Invalid parameters for FindMaCISL. Search skipped.");
    result = KMaCIInvalidParam;

  }

  return result;
}
//*****************************************************************************

MaCI::EMaCIError
CMaCICtrlClient::RequestAuthentication(const std::string &aGIMnetName,
                                       const unsigned int aMajor,
                                       const unsigned int aMinor,
                                       const TAuthentication &aAuthentication,
                                       TAccess &aAccess,
                                       const int aMaCICtrlMinor,
                                       const unsigned int aTimeout_ms) const
{
  MaCI::EMaCIError result = KMaCIError;

  bool req_init;
  // Create request.
  CMaCICtrlData req;
  req.CreateInternalBinBag();
  req_init = req.SetTimestamp(Common::TTimestamp());
  req_init &= req.SetCommand(KCommandSetAuthentication);
  req_init &= req.AddService(TService(aMajor, aMinor));
  req_init &= req.SetAuthentication(aAuthentication);
  assert(req_init == true);

  // Add Accepted service
  iGIMIPtr->addAcceptedService(iInterfaceMajor,
                               aMaCICtrlMinor,
                               "MaCI-MaCICtrlClient");

  // Create local redirection to allow getting right answer back
  const unsigned int redir = iGIMIPtr->reserveBufferIdentifier();
  iGIMIPtr->addRedirection(redir,
                           iInterfaceMajor,
                           aMaCICtrlMinor,
                           aGIMnetName);

  // Send the request
  const int r = iGIMIPtr->send(aGIMnetName,
                               req.GetBinBagContainerPtr()->GetBuffer(),
                               req.GetBinBagContainerPtr()->GetBufferSize(),
                               iInterfaceMajor,
                               aMaCICtrlMinor);
  if (r != GIMI_OK) {
    switch(r) {
    case GIMI_NOCONNECTION:
      result = KMaCINoConnection;
    case GIMI_INTERRUPT:
    case GIMI_ERROR:
    default:
      // Any error, print warning and sleep for one second.
      dPrint(6,"GIMI Error (%d, '%s') - from send() received",
             r, gimi::getGimiErrorString(r).c_str());
      break;
    }

  } else {
    dPrint(8,"Authentication Request Sent");
    // Request was succesfully sent, so stand back and synchronously wait for reply.
    gimi::GIMIMessage msg;
    const int gr = iGIMIPtr->receive(msg,
                                     aTimeout_ms,
                                     iInterfaceMajor,
                                     aMaCICtrlMinor,
                                     redir);
    if (gr != GIMI_OK) {
      switch(gr) {
      case GIMI_TIMEOUT:
        result = KMaCITimeout;
      case GIMI_NOCONNECTION:
        if (result == KMaCIError) result = KMaCINoConnection;
      case GIMI_INTERRUPT:
      case GIMI_ERROR:
      default:
        // Any error, print warning and sleep for one second.
        dPrint(6,"GIMI Error (%d, '%s') - from receive() received",
               gr, gimi::getGimiErrorString(gr).c_str());
        break;
      }
    } else {
      // Receive OK. (Got message, decode it)
      if (DecodeAccessMessage(aAccess, msg) == true) {
        result = KMaCIOK;
        dPrint(7,"Access message Parsed OK.");

      } else {
        dPrint(7,"Access message parsing FAILED!");

      }
    }
  }

  // Remove the local redirection
  iGIMIPtr->removeRedirection(redir,
                              iInterfaceMajor,
                              aMaCICtrlMinor,
                              aGIMnetName);
  iGIMIPtr->releaseBufferIdentifier(redir);

  // Remove accepted service
  iGIMIPtr->removeAcceptedService(iInterfaceMajor,
                                  aMaCICtrlMinor);

  return result;
}
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
//*****************************************************************************
